package com.piece.blog.service;

import com.piece.blog.dto.TrainingExamDTO;
import com.piece.blog.dto.TrainingQuestionDTO;
import com.piece.blog.model.TrainingExamInfo;
import com.piece.blog.model.TrainingQuestion;
import com.piece.blog.repository.TrainingExamInfoRepository;
import com.piece.blog.repository.TrainingQuestionRepository;
import com.piece.core.framework.constant.ExceptionConstants;
import com.piece.core.framework.exception.ErrorCode;
import com.piece.core.framework.exception.FrameWorkException;
import com.piece.core.framework.support.bootstrap.DataTable;
import com.piece.core.framework.support.bootstrap.QueryTable;
import com.piece.core.framework.support.convert.Convert;
import com.piece.core.framework.util.collection.ArrayUtil;
import com.piece.core.framework.util.object.BeanUtil;
import com.piece.core.framework.util.string.StringUtil;
import com.piece.core.jpa.repository.BaseService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

@Slf4j
@Service
@Transactional
public class TrainingExamInfoService extends BaseService<TrainingExamInfo, String> {

    @Autowired
    private TrainingExamInfoRepository trainingExamInfoRepository;

    @Autowired
    private TrainingQuestionRepository trainingQuestionRepository;

    @Autowired
    private TrainingQuestionService trainingQuestionService;

    @Resource
    private JdbcTemplate jdbcTemplate;

    @Resource
    private ThreadPoolTaskExecutor taskExecutor;

    @PostConstruct
    public void initRepository() {
        setRepository(trainingExamInfoRepository);
    }

    /**
     * 查询试卷考题
     */
    public DataTable findQuestionPage(QueryTable queryTable, Long examId) {
        String hql = "select q.* from `training_question` q " +
                " where question_id in(select question_id from `training_exam_question` where exam_id = " + examId + ")";
        queryTable.setAlias("q");
        queryTable.setHql(hql);
        return trainingQuestionRepository.findPageBySql(queryTable);
    }

    public DataTable findQuestionPageWithoutExam(QueryTable queryTable, Long examId) {
        String hql = "select q.* from `training_question` q " +
                " where question_id not in(select question_id from `training_exam_question` where exam_id = " + examId + ")";
        queryTable.setAlias("q");
        queryTable.setHql(hql);
        return trainingQuestionRepository.findPageBySql(queryTable);
    }

    public void addQuestion(Long examId, List<Long> questionIds) {
        String sql = "insert `training_exam_question` values(%d, %d)";
        questionIds.stream().forEach(questionId -> {
            jdbcTemplate.execute(String.format(sql, examId, questionId));
        });
    }

    @Override
    public void delete(Serializable examId) throws Exception {
        super.delete(examId);
        jdbcTemplate.execute("delete from `training_exam_question` where exam_id = " + examId);
    }

    @Override
    public int delete(Serializable[] examIds) throws Exception {
        int result = super.delete(examIds);
        jdbcTemplate.execute("delete from `training_exam_question` where exam_id in(" + StringUtil.join(examIds, ',') + ")");
        return result;
    }

    public void deleteQuestion(Long examId, String questionIds) throws Exception {
        jdbcTemplate.execute("delete from `training_exam_question` where exam_id = " + examId + " and question_id in(" + questionIds + ")");
    }

    public TrainingExamDTO preview(Long examId) {
        TrainingExamInfo examInfo = trainingExamInfoRepository.findById(examId);
        if (null == examInfo) {
            throw new FrameWorkException(new ErrorCode(ExceptionConstants.NOT_EXIST, "试卷"));
        }

        TrainingExamDTO exam = new TrainingExamDTO();
        BeanUtil.copyProperties(examInfo, exam, true);
        boolean reSortQuestion = "0".equals(examInfo.getQuestionDisorder()) ? true : false;
        boolean reSortOption = "0".equals(examInfo.getOptionDisorder()) ? true : false;

        String sql = "select question_id from `training_exam_question` where exam_id = " + examId;
        List<Long> questionIds = jdbcTemplate.queryForList(sql, Long.class);
        List<TrainingQuestionDTO> questions = new ArrayList<>();
        questionIds.stream().forEach(q_id -> {
            TrainingQuestionDTO question = trainingQuestionService.loadByQuestionId(q_id, reSortOption);
            question.setAnswer(null);
            questions.add(question);
        });

        if (reSortQuestion) {
            Collections.shuffle(questions);
        }

        exam.setQuestions(questions);
        return exam;
    }

    public TrainingExamDTO score(TrainingExamDTO exam) throws Exception {
        TrainingExamInfo examInfo = trainingExamInfoRepository.findById(exam.getExamId());
        if (null == examInfo) {
            throw new FrameWorkException(new ErrorCode(ExceptionConstants.NOT_EXIST, "试卷"));
        }

        ThreadPoolExecutor executor = taskExecutor.getThreadPoolExecutor();
        List<TrainingQuestionDTO> questions = exam.getQuestions();
        List<CompletableFuture> futures = new ArrayList<>();
        questions.stream().forEach(question -> futures.add(CompletableFuture.supplyAsync(() -> {
            TrainingQuestion q = trainingQuestionRepository.findById(question.getQuestionId());
            if (checkAnswer(q.getAnswer(), question.getAnswer())) {
                question.setIsCorrect(true);
                return q.getScore();
            }

            question.setIsCorrect(false);
            return 0L;
        }, executor)));

        Long score = 0L;
        CompletableFuture<Void> allFuture = CompletableFuture.allOf(ArrayUtil.convertArray(futures));
        allFuture.get();
        for (int i=0; i< futures.size(); i++) {
            score += (Convert.toLong(futures.get(i).get(), 0L));
        }

        exam.setScore(score);
        return exam;
    }

    /**
     * 比对答案
     * @param q1 正确答案
     * @param q2 用户答案
     * @return
     */
    private boolean checkAnswer(String q1, String q2) {
        if (q1.indexOf("{:}") != -1) {
            String[] correctAnswers = q1.split("\\{:\\}");
            String[] userAnswers = q2.split("\\{:\\}");
            if (correctAnswers.length == userAnswers.length) {
                return Arrays.asList(correctAnswers).stream().sorted().collect(Collectors.joining())
                        .equals(Arrays.asList(userAnswers).stream().sorted().collect(Collectors.joining()));
            }

            return false;
        }
        return q1.equals(q2);
    }
}
